<!DOCTYPE html>
<meta charset="utf-8">
<body>
<script src="../node_modules/d3/dist/d3.js"></script>
<script src="../node_modules/@hpcc-js/wasm/dist/graphviz.umd.js" type="text/javascript"></script>
<script src="../build/d3-graphviz.js"></script>
<div id="graph" style="text-align: center;"></div>
<script>

'use strict';

var margin = 20; // to avoid scrollbars

var isDrawingEdge = false;
var isDrawingNode = false;
var startNode;
var selectedEdge = d3.select(null);
var selectedEdgeFill;
var selectedEdgeStroke;
var selectedNode = d3.select(null);
var selectedNodeStroke;
var selectedNodeFill;

var nodeIndex;
var edgeIndex;
var prevNodeName = 'b';

var shapes = [
  "box",
  "polygon",
  "ellipse",
  "oval",

  "circle",
  "point",
  "egg",
  "triangle",

  "plaintext",
  "plain",
  "diamond",
  "trapezium",

  "parallelogram",
  "house",
  "pentagon",
  "hexagon",

  "septagon",
  "octagon",
  "doublecircle",
  "doubleoctagon",

  "tripleoctagon",
  "invtriangle",
  "invtrapezium",
  "invhouse",

  "Mdiamond",
  "Msquare",
  "Mcircle",
  "rect",

  "rectangle",
  "square",
  "star",
  "none",

  "underline",
  "cylinder",
  "note",
  "tab",

  "folder",
  "box3d",
  "component",
  "promoter",

  "cds",
  "terminator",
  "utr",
  "primersite",

  "restrictionsite",
  "fivepoverhang",
  "threepoverhang",
  "noverhang",

  "assembly",
  "signature",
  "insulator",
  "ribosite",

  "rnastab",
  "proteasesite",
  "proteinstab",
  "rpromoter",

  "rarrow",
  "larrow",
  "lpromoter",
];

var dotSrc = `strict digraph {
    node [style="filled"]
    a [shape="box" fillcolor="` + d3.schemeCategory10[0] + `"]
    b [shape="polygon" fillcolor="` + d3.schemeCategory10[1] + `"]
    a -> b [fillcolor="` + d3.schemePaired[0] + `" color="` + d3.schemePaired[1] + `"]
}`;

var graphviz = d3.select("#graph").graphviz()
    .attributer(attributer)
    .transition(function() {
        return d3.transition().duration(1000);
    });

render();

function attributer(datum, index, nodes) {
    var selection = d3.select(this);
    if (datum.tag == "svg") {
        var width = window.innerWidth - margin;
        var height = window.innerHeight - margin;
        var x = "10";
        var y = "10";
        var unit = 'px';
        selection
            .attr("width", width + unit)
            .attr("height", height + unit);
        datum.attributes.width = width + unit;
        datum.attributes.height = height + unit;
    }
}

function render() {
    graphviz
        .renderDot(dotSrc, startApp);
}

function startApp() {
    var nodes = d3.selectAll(".node");
    var edges = d3.selectAll(".edge");

    // click outside of nodes
    d3.select(document).on("click mousedown", function(event) {
        event.preventDefault();
        event.stopPropagation();
        console.log('document click or mousedown');
        unSelectEdge();
        if (event.which == 2) {
            var graph0 = d3.select("#graph").selectWithoutDataPropagation("svg").selectWithoutDataPropagation("g");
            var width = 54;
            var height = 36;
            var [x0, y0] = d3.pointer(event, graph0.node());
            var x1 = x0 - width / 2;
            var x2 = x0 + width / 2;
            var y1 = y0 - height / 2;
            var y2 = y0 + height / 2;
            if (nodeIndex == null) {
                nodeIndex = d3.selectAll('.node').size();
            } else {
                nodeIndex += 1;
            }
            var shape = shapes[nodeIndex % shapes.length];
            var numLetters = 'z'.charCodeAt(0) - 'a'.charCodeAt(0) + 1;
            var letter = String.fromCharCode('a'.charCodeAt(0) + (nodeIndex % numLetters));
            var digit = Math.floor(nodeIndex / numLetters) || '';
            var nodeName = letter + digit;
            var fillcolor = d3.schemeCategory10[nodeIndex % 10];
            var color = 'black';
            graphviz
                .drawNode(x1, y1, nodeName, {shape: shape, text: nodeName, fillcolor: fillcolor, color: color})
                .insertDrawnNode(nodeName);
            var dotSrcLines = dotSrc.split('\n');
            var newNodeString = nodeName + ' [shape="' + shape + '" color="' + color + '"; fillcolor="' + fillcolor + '"; penwidth="1"]';
            dotSrcLines.splice(-1, 0, newNodeString);
            dotSrc = dotSrcLines.join('\n');
            render();
        }
    });

    // keyup outside of nodes
    d3.select(document).on("keyup", function(event) {
        event.preventDefault();
        console.log('document keyup', event);
        if (event.keyCode == 27) {
            graphviz.removeDrawnEdge();
            unSelectEdge();
        }
        if (event.keyCode == 46) {
            deleteSelectedEdge();
            deleteSelectedNode();
            graphviz.removeDrawnEdge();
            render();
        }
        isDrawingEdge = false;
    });

    // move
    d3.select(document).on("mousemove", function(event) {
        event.preventDefault();
        event.stopPropagation();
        console.log('document mousemove');
        var graph0 = d3.select("#graph").selectWithoutDataPropagation("svg").selectWithoutDataPropagation("g");
        var [x0, y0] = d3.pointer(event, graph0.node());
        var shortening = 2; // avoid mouse pointing on edge
        if (isDrawingEdge) {
            graphviz
                .moveDrawnEdgeEndPoint(x0, y0,  {shortening: shortening})
        }
    });

    // click and mousedown on nodes
    nodes.on("click mousedown", function(event) {
        event.preventDefault();
        console.log('node click or mousedown');
        if (!isDrawingEdge && event.which == 1) {
            unSelectEdge();
            selectNode(d3.select(this));
        }
    });

    // double-click on nodes
    nodes.on("dblclick", function(event) {
        event.preventDefault();
        event.stopPropagation();
        console.log('node dblclick');
        unSelectEdge();
        if (isDrawingEdge) {
            var endNode = d3.select(this);
            var startNodeName = startNode.selectWithoutDataPropagation("title").text();
            var endNodeName = endNode.selectWithoutDataPropagation("title").text();
            graphviz
                .insertDrawnEdge(startNodeName + '->' + endNodeName);
            var fillcolor = d3.schemePaired[(edgeIndex * 2) % 12];
            var color = d3.schemePaired[(edgeIndex * 2 + 1) % 12];
            var dotSrcLines = dotSrc.split('\n');
            var newEdgeString = startNodeName + ' -> ' + endNodeName + ' [color="' + color + '"; fillcolor="' + fillcolor + '"; penwidth="1"]';
            dotSrcLines.splice(-1, 0, newEdgeString);
            dotSrc = dotSrcLines.join('\n');
            render();
        }
        isDrawingEdge = false;
    });

    // right-click on nodes
    nodes.on("contextmenu", function(event) {
        event.preventDefault();
        event.stopPropagation();
        console.log('node contextmenu');
        unSelectEdge();
        unSelectNode();
        graphviz.removeDrawnEdge();
        startNode = d3.select(this);
        var graph0 = d3.select("#graph").selectWithoutDataPropagation("svg").selectWithoutDataPropagation("g");
        var [x0, y0] = d3.pointer(event, graph0.node());
            if (edgeIndex == null) {
                edgeIndex = d3.selectAll('.edge').size();
            } else {
                edgeIndex += 1;
            }
        var fillcolor = d3.schemePaired[(edgeIndex * 2) % 12];
        var color = d3.schemePaired[(edgeIndex * 2 + 1) % 12];

        graphviz
            .drawEdge(x0, y0, x0, y0, {fillcolor: fillcolor, color: color});
        isDrawingEdge = true;
    });

    // click and mousedown on edges
    edges.on("click mousedown", function(event) {
        event.preventDefault();
        event.stopPropagation();
        console.log('node click or mousedown');
        unSelectNode();
        selectEdge(d3.select(this));
    });

    // right-click outside of nodes
    d3.select(document).on("contextmenu", function(event) {
        event.preventDefault();
        event.stopPropagation();
        console.log('document contextmenu');
        unSelectEdge();
    });

}

function selectEdge(edge) {
    unSelectEdge();
    selectedEdge = edge;
    selectedEdgeFill = selectedEdge.selectAll('polygon').attr("fill");
    selectedEdgeStroke = selectedEdge.selectAll('polygon').attr("stroke");
    selectedEdge.selectAll('path, polygon').attr("stroke", "red");
    selectedEdge.selectAll('polygon').attr("fill", "red");
}

function unSelectEdge() {
    selectedEdge.selectAll('path, polygon').attr("stroke", selectedEdgeStroke);
    selectedEdge.selectAll('polygon').attr("fill", selectedEdgeFill);
    selectedEdge = d3.select(null);
}

function deleteSelectedEdge() {
    selectedEdge.style("display", "none");
    if (selectedEdge.size() != 0) {
        var edgeName = selectedEdge.selectWithoutDataPropagation("title").text();
        edgeName = edgeName.replace('->', ' -> ');
        var dotSrcLines = dotSrc.split('\n');
        while (true) {
            var i = dotSrcLines.findIndex(function (element, index) {
                return element.indexOf(edgeName) >= 0;
            });
            if (i < 0)
                break;
            dotSrcLines.splice(i, 1);
        }
        dotSrc = dotSrcLines.join('\n');
    }
}

function selectNode(node) {
    unSelectNode();
    selectedNode = node;
    selectedNodeFill = selectedNode.selectAll('polygon, ellipse').attr("fill");
    selectedNodeStroke = selectedNode.selectAll('polygon, ellipse').attr("stroke");
    selectedNode.selectAll('polygon, ellipse').attr("stroke", "red");
    selectedNode.selectAll('polygon, ellipse').attr("fill", "red");
}

function unSelectNode() {
    selectedNode.selectAll('polygon, ellipse').attr("stroke", selectedNodeStroke);
    selectedNode.selectAll('polygon, ellipse').attr("fill", selectedNodeFill);
    selectedNode = d3.select(null);
}

function deleteSelectedNode() {
    selectedNode.style("display", "none");
    if (selectedNode.size() != 0) {
        var nodeName = selectedNode.selectWithoutDataPropagation("title").text();
        var dotSrcLines = dotSrc.split('\n');
        while (true) {
            var i = dotSrcLines.findIndex(function (element, index) {
                var trimmedElement = element.trim();
                if (trimmedElement == nodeName) {
                    return true;
                }
                if (trimmedElement.indexOf(nodeName + ' ') == 0) {
                    return true;
                }
                if (trimmedElement.indexOf(' ' + nodeName + ' ') >= 0) {
                    return true;
                }
                return false;
            });
            if (i < 0)
                break;
            dotSrcLines.splice(i, 1);
        }
        dotSrc = dotSrcLines.join('\n');
    }
}

</script>
